mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-24 12:06:15 +01:00
[PM-13454] Get hostname for login uri (#11646)
* add uri to raw data * add uri * PM-13454 modify the hostnames to friendly names * PM-13454 removed commented code * add password health service * add spec. fix logic in password reuse * PM-13454 added member count and group by uris * PM-13454 removed moved files * PM-13454 fixed linting errors and failed unit tests * PM-13454 grouping member count * PM-13454 added unit test for totalGroupedMembersMap * PM-13454 removed the grouping - show a flatmap --------- Co-authored-by: jaasen-livefront <jaasen@livefront.com>
This commit is contained in:
parent
82d4fe4d66
commit
ab3d760dfd
@ -22,6 +22,9 @@
|
|||||||
<bit-tab label="Raw Data + members">
|
<bit-tab label="Raw Data + members">
|
||||||
<tools-password-health-members></tools-password-health-members>
|
<tools-password-health-members></tools-password-health-members>
|
||||||
</bit-tab>
|
</bit-tab>
|
||||||
|
<bit-tab label="Raw Data + uri">
|
||||||
|
<tools-password-health-members-uri></tools-password-health-members-uri>
|
||||||
|
</bit-tab>
|
||||||
<!-- <bit-tab label="{{ 'allApplicationsWithCount' | i18n: apps.length }}">
|
<!-- <bit-tab label="{{ 'allApplicationsWithCount' | i18n: apps.length }}">
|
||||||
<h2 bitTypography="h2">{{ "allApplications" | i18n }}</h2>
|
<h2 bitTypography="h2">{{ "allApplications" | i18n }}</h2>
|
||||||
<tools-application-table></tools-application-table>
|
<tools-application-table></tools-application-table>
|
||||||
|
@ -11,6 +11,7 @@ import { HeaderModule } from "../../layouts/header/header.module";
|
|||||||
|
|
||||||
import { ApplicationTableComponent } from "./application-table.component";
|
import { ApplicationTableComponent } from "./application-table.component";
|
||||||
import { NotifiedMembersTableComponent } from "./notified-members-table.component";
|
import { NotifiedMembersTableComponent } from "./notified-members-table.component";
|
||||||
|
import { PasswordHealthMembersURIComponent } from "./password-health-members-uri.component";
|
||||||
import { PasswordHealthMembersComponent } from "./password-health-members.component";
|
import { PasswordHealthMembersComponent } from "./password-health-members.component";
|
||||||
import { PasswordHealthComponent } from "./password-health.component";
|
import { PasswordHealthComponent } from "./password-health.component";
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ export enum AccessIntelligenceTabType {
|
|||||||
HeaderModule,
|
HeaderModule,
|
||||||
PasswordHealthComponent,
|
PasswordHealthComponent,
|
||||||
PasswordHealthMembersComponent,
|
PasswordHealthMembersComponent,
|
||||||
|
PasswordHealthMembersURIComponent,
|
||||||
NotifiedMembersTableComponent,
|
NotifiedMembersTableComponent,
|
||||||
TabsModule,
|
TabsModule,
|
||||||
],
|
],
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
<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 bitSortable="hostURI">{{ "application" | 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>
|
||||||
|
<th bitCell class="tw-text-right">{{ "totalMembers" | i18n }}</th>
|
||||||
|
</tr>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template body let-rows$>
|
||||||
|
<tr bitRow *ngFor="let r of rows$ | async">
|
||||||
|
<td bitCell>
|
||||||
|
<ng-container>
|
||||||
|
<span>{{ r.hostURI }}</span>
|
||||||
|
</ng-container>
|
||||||
|
</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>
|
||||||
|
<td bitCell class="tw-text-right" data-testid="total-membership">
|
||||||
|
{{ totalMembersMap.get(r.id) || 0 }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</bit-table>
|
||||||
|
</div>
|
||||||
|
</bit-container>
|
@ -0,0 +1,61 @@
|
|||||||
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
|
import { ActivatedRoute, convertToParamMap } from "@angular/router";
|
||||||
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
import { of } from "rxjs";
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/access-intelligence";
|
||||||
|
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
|
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 { LooseComponentsModule } from "../../shared";
|
||||||
|
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||||
|
|
||||||
|
import { PasswordHealthMembersURIComponent } from "./password-health-members-uri.component";
|
||||||
|
|
||||||
|
describe("PasswordHealthMembersUriComponent", () => {
|
||||||
|
let component: PasswordHealthMembersURIComponent;
|
||||||
|
let fixture: ComponentFixture<PasswordHealthMembersURIComponent>;
|
||||||
|
let cipherServiceMock: MockProxy<CipherService>;
|
||||||
|
const passwordHealthServiceMock = mock<PasswordHealthService>();
|
||||||
|
|
||||||
|
const activeRouteParams = convertToParamMap({ organizationId: "orgId" });
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
cipherServiceMock = mock<CipherService>();
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [PasswordHealthMembersURIComponent, PipesModule, TableModule, LooseComponentsModule],
|
||||||
|
providers: [
|
||||||
|
{ provide: CipherService, useValue: cipherServiceMock },
|
||||||
|
{ provide: I18nService, useValue: mock<I18nService>() },
|
||||||
|
{ provide: AuditService, useValue: mock<AuditService>() },
|
||||||
|
{ provide: OrganizationService, useValue: mock<OrganizationService>() },
|
||||||
|
{
|
||||||
|
provide: PasswordStrengthServiceAbstraction,
|
||||||
|
useValue: mock<PasswordStrengthServiceAbstraction>(),
|
||||||
|
},
|
||||||
|
{ provide: PasswordHealthService, useValue: passwordHealthServiceMock },
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: {
|
||||||
|
paramMap: of(activeRouteParams),
|
||||||
|
url: of([]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(PasswordHealthMembersURIComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should initialize component", () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,108 @@
|
|||||||
|
import { CommonModule } from "@angular/common";
|
||||||
|
import { Component, DestroyRef, inject, OnInit } from "@angular/core";
|
||||||
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { map } from "rxjs";
|
||||||
|
|
||||||
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { PasswordHealthService } from "@bitwarden/bit-common/tools/reports/access-intelligence";
|
||||||
|
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 { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
|
import {
|
||||||
|
BadgeModule,
|
||||||
|
BadgeVariant,
|
||||||
|
ContainerComponent,
|
||||||
|
TableDataSource,
|
||||||
|
TableModule,
|
||||||
|
} from "@bitwarden/components";
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { HeaderModule } from "../../layouts/header/header.module";
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { OrganizationBadgeModule } from "../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { PipesModule } from "../../vault/individual-vault/pipes/pipes.module";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: "tools-password-health-members-uri",
|
||||||
|
templateUrl: "password-health-members-uri.component.html",
|
||||||
|
imports: [
|
||||||
|
BadgeModule,
|
||||||
|
OrganizationBadgeModule,
|
||||||
|
CommonModule,
|
||||||
|
ContainerComponent,
|
||||||
|
PipesModule,
|
||||||
|
JslibModule,
|
||||||
|
HeaderModule,
|
||||||
|
TableModule,
|
||||||
|
],
|
||||||
|
providers: [PasswordHealthService],
|
||||||
|
})
|
||||||
|
export class PasswordHealthMembersURIComponent implements OnInit {
|
||||||
|
passwordStrengthMap = new Map<string, [string, BadgeVariant]>();
|
||||||
|
|
||||||
|
weakPasswordCiphers: CipherView[] = [];
|
||||||
|
|
||||||
|
passwordUseMap = new Map<string, number>();
|
||||||
|
|
||||||
|
exposedPasswordMap = new Map<string, number>();
|
||||||
|
|
||||||
|
totalMembersMap = new Map<string, number>();
|
||||||
|
|
||||||
|
dataSource = new TableDataSource<CipherView>();
|
||||||
|
|
||||||
|
reportCiphers: (CipherView & { hostURI: string })[] = [];
|
||||||
|
reportCipherURIs: string[] = [];
|
||||||
|
|
||||||
|
organization: Organization;
|
||||||
|
|
||||||
|
loading = true;
|
||||||
|
|
||||||
|
private destroyRef = inject(DestroyRef);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected cipherService: CipherService,
|
||||||
|
protected passwordStrengthService: PasswordStrengthServiceAbstraction,
|
||||||
|
protected organizationService: OrganizationService,
|
||||||
|
protected auditService: AuditService,
|
||||||
|
protected i18nService: I18nService,
|
||||||
|
protected activatedRoute: ActivatedRoute,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.activatedRoute.paramMap
|
||||||
|
.pipe(
|
||||||
|
takeUntilDestroyed(this.destroyRef),
|
||||||
|
map(async (params) => {
|
||||||
|
const organizationId = params.get("organizationId");
|
||||||
|
await this.setCiphers(organizationId);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
async setCiphers(organizationId: string) {
|
||||||
|
const passwordHealthService = new PasswordHealthService(
|
||||||
|
this.passwordStrengthService,
|
||||||
|
this.auditService,
|
||||||
|
this.cipherService,
|
||||||
|
organizationId,
|
||||||
|
);
|
||||||
|
|
||||||
|
await passwordHealthService.generateReport();
|
||||||
|
|
||||||
|
this.dataSource.data = passwordHealthService.groupCiphersByLoginUri();
|
||||||
|
this.exposedPasswordMap = passwordHealthService.exposedPasswordMap;
|
||||||
|
this.passwordStrengthMap = passwordHealthService.passwordStrengthMap;
|
||||||
|
this.passwordUseMap = passwordHealthService.passwordUseMap;
|
||||||
|
this.totalMembersMap = passwordHealthService.totalMembersMap;
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,13 @@ export const mockCiphers: any[] = [
|
|||||||
organizationUseTotp: false,
|
organizationUseTotp: false,
|
||||||
login: {
|
login: {
|
||||||
password: "123",
|
password: "123",
|
||||||
|
hasUris: true,
|
||||||
|
uris: [
|
||||||
|
{ uri: "www.google.com" },
|
||||||
|
{ uri: "accounts.google.com" },
|
||||||
|
{ uri: "https://www.google.com" },
|
||||||
|
{ uri: "https://www.google.com/login" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
edit: false,
|
edit: false,
|
||||||
viewPassword: true,
|
viewPassword: true,
|
||||||
|
@ -64,5 +64,16 @@ export const mockMemberCipherDetailsResponse: { data: any[] } = {
|
|||||||
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
|
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
UserName: "Chris Finch Tester",
|
||||||
|
Email: "chris.finch@wernhamhogg.uk",
|
||||||
|
UsesKeyConnector: true,
|
||||||
|
cipherIds: [
|
||||||
|
"cbea34a8-bde4-46ad-9d19-b05001228ab2",
|
||||||
|
"cbea34a8-bde4-46ad-9d19-b05001228cd3",
|
||||||
|
"cbea34a8-bde4-46ad-9d19-b05001228xy4",
|
||||||
|
"cbea34a8-bde4-46ad-9d19-b05001228ab1",
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
@ -99,12 +99,12 @@ describe("PasswordHealthService", () => {
|
|||||||
|
|
||||||
it("should calculate total members per cipher", () => {
|
it("should calculate total members per cipher", () => {
|
||||||
expect(service.totalMembersMap.size).toBeGreaterThan(0);
|
expect(service.totalMembersMap.size).toBeGreaterThan(0);
|
||||||
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab1")).toBe(2);
|
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab1")).toBe(3);
|
||||||
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab2")).toBe(4);
|
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228ab2")).toBe(5);
|
||||||
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228cd3")).toBe(5);
|
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228cd3")).toBe(6);
|
||||||
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001227nm5")).toBe(4);
|
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001227nm5")).toBe(4);
|
||||||
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001227nm7")).toBe(1);
|
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001227nm7")).toBe(1);
|
||||||
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228xy4")).toBe(6);
|
expect(service.totalMembersMap.get("cbea34a8-bde4-46ad-9d19-b05001228xy4")).toBe(7);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { Inject, Injectable } from "@angular/core";
|
import { Inject, Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { mockCiphers } from "@bitwarden/bit-common/tools/reports/access-intelligence/services/ciphers.mock";
|
||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { mockMemberCipherDetailsResponse } from "@bitwarden/bit-common/tools/reports/access-intelligence/services/member-cipher-details-response.mock";
|
||||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||||
@ -8,9 +12,6 @@ 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 { BadgeVariant } from "@bitwarden/components";
|
import { BadgeVariant } from "@bitwarden/components";
|
||||||
|
|
||||||
import { mockCiphers } from "./ciphers.mock";
|
|
||||||
import { mockMemberCipherDetailsResponse } from "./member-cipher-details-response.mock";
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PasswordHealthService {
|
export class PasswordHealthService {
|
||||||
reportCiphers: CipherView[] = [];
|
reportCiphers: CipherView[] = [];
|
||||||
@ -157,10 +158,27 @@ export class PasswordHealthService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkForExistingCipher(ciph: CipherView) {
|
checkForExistingCipher(ciph: CipherView) {
|
||||||
if (!this.reportCipherIds.includes(ciph.id)) {
|
if (!this.reportCipherIds.includes(ciph.id)) {
|
||||||
this.reportCipherIds.push(ciph.id);
|
this.reportCipherIds.push(ciph.id);
|
||||||
this.reportCiphers.push(ciph);
|
this.reportCiphers.push(ciph);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
groupCiphersByLoginUri(): CipherView[] {
|
||||||
|
const cipherViews: CipherView[] = [];
|
||||||
|
const cipherUris: string[] = [];
|
||||||
|
const ciphers = this.reportCiphers;
|
||||||
|
|
||||||
|
ciphers.forEach((ciph) => {
|
||||||
|
const uris = ciph.login?.uris ?? [];
|
||||||
|
uris.map((u: { uri: string }) => {
|
||||||
|
const uri = Utils.getHostname(u.uri).replace("www.", "");
|
||||||
|
cipherUris.push(uri);
|
||||||
|
cipherViews.push({ ...ciph, hostURI: uri } as CipherView & { hostURI: string });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return cipherViews;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user