mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 14:23:32 +00:00
AC-1333 vault report org ciphers (#5998)
* updated report components to only show can edit ciphers, added badges, spec files --------- Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
@@ -4,7 +4,6 @@ import { ActivatedRoute } from "@angular/router";
|
|||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
@@ -25,12 +24,11 @@ export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportC
|
|||||||
cipherService: CipherService,
|
cipherService: CipherService,
|
||||||
auditService: AuditService,
|
auditService: AuditService,
|
||||||
modalService: ModalService,
|
modalService: ModalService,
|
||||||
messagingService: MessagingService,
|
organizationService: OrganizationService,
|
||||||
private organizationService: OrganizationService,
|
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
passwordRepromptService: PasswordRepromptService
|
passwordRepromptService: PasswordRepromptService
|
||||||
) {
|
) {
|
||||||
super(cipherService, auditService, modalService, messagingService, passwordRepromptService);
|
super(cipherService, auditService, organizationService, modalService, passwordRepromptService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { ActivatedRoute } from "@angular/router";
|
|||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||||
@@ -21,13 +20,12 @@ export class InactiveTwoFactorReportComponent extends BaseInactiveTwoFactorRepor
|
|||||||
constructor(
|
constructor(
|
||||||
cipherService: CipherService,
|
cipherService: CipherService,
|
||||||
modalService: ModalService,
|
modalService: ModalService,
|
||||||
messagingService: MessagingService,
|
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
passwordRepromptService: PasswordRepromptService,
|
passwordRepromptService: PasswordRepromptService,
|
||||||
private organizationService: OrganizationService
|
organizationService: OrganizationService
|
||||||
) {
|
) {
|
||||||
super(cipherService, modalService, messagingService, logService, passwordRepromptService);
|
super(cipherService, organizationService, modalService, logService, passwordRepromptService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { ActivatedRoute } from "@angular/router";
|
|||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
@@ -24,13 +22,11 @@ export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportCom
|
|||||||
constructor(
|
constructor(
|
||||||
cipherService: CipherService,
|
cipherService: CipherService,
|
||||||
modalService: ModalService,
|
modalService: ModalService,
|
||||||
messagingService: MessagingService,
|
|
||||||
stateService: StateService,
|
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private organizationService: OrganizationService,
|
organizationService: OrganizationService,
|
||||||
passwordRepromptService: PasswordRepromptService
|
passwordRepromptService: PasswordRepromptService
|
||||||
) {
|
) {
|
||||||
super(cipherService, modalService, messagingService, stateService, passwordRepromptService);
|
super(cipherService, organizationService, modalService, passwordRepromptService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ActivatedRoute } from "@angular/router";
|
|||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||||
@@ -20,12 +19,11 @@ export class UnsecuredWebsitesReportComponent extends BaseUnsecuredWebsitesRepor
|
|||||||
constructor(
|
constructor(
|
||||||
cipherService: CipherService,
|
cipherService: CipherService,
|
||||||
modalService: ModalService,
|
modalService: ModalService,
|
||||||
messagingService: MessagingService,
|
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private organizationService: OrganizationService,
|
organizationService: OrganizationService,
|
||||||
passwordRepromptService: PasswordRepromptService
|
passwordRepromptService: PasswordRepromptService
|
||||||
) {
|
) {
|
||||||
super(cipherService, modalService, messagingService, passwordRepromptService);
|
super(cipherService, organizationService, modalService, passwordRepromptService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ActivatedRoute } from "@angular/router";
|
|||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
|
||||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||||
@@ -25,16 +24,15 @@ export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportCompone
|
|||||||
cipherService: CipherService,
|
cipherService: CipherService,
|
||||||
passwordStrengthService: PasswordStrengthServiceAbstraction,
|
passwordStrengthService: PasswordStrengthServiceAbstraction,
|
||||||
modalService: ModalService,
|
modalService: ModalService,
|
||||||
messagingService: MessagingService,
|
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private organizationService: OrganizationService,
|
organizationService: OrganizationService,
|
||||||
passwordRepromptService: PasswordRepromptService
|
passwordRepromptService: PasswordRepromptService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
cipherService,
|
cipherService,
|
||||||
passwordStrengthService,
|
passwordStrengthService,
|
||||||
|
organizationService,
|
||||||
modalService,
|
modalService,
|
||||||
messagingService,
|
|
||||||
passwordRepromptService
|
passwordRepromptService
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
|
import { Directive, ViewChild, ViewContainerRef } from "@angular/core";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
|
||||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
import { PasswordRepromptService } from "@bitwarden/vault";
|
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||||
@@ -19,13 +20,15 @@ export class CipherReportComponent {
|
|||||||
hasLoaded = false;
|
hasLoaded = false;
|
||||||
ciphers: CipherView[] = [];
|
ciphers: CipherView[] = [];
|
||||||
organization: Organization;
|
organization: Organization;
|
||||||
|
organizations$: Observable<Organization[]>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private modalService: ModalService,
|
private modalService: ModalService,
|
||||||
protected messagingService: MessagingService,
|
protected passwordRepromptService: PasswordRepromptService,
|
||||||
public requiresPaid: boolean,
|
protected organizationService: OrganizationService
|
||||||
protected passwordRepromptService: PasswordRepromptService
|
) {
|
||||||
) {}
|
this.organizations$ = this.organizationService.organizations$;
|
||||||
|
}
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|||||||
@@ -49,6 +49,16 @@
|
|||||||
<br />
|
<br />
|
||||||
<small>{{ c.subTitle }}</small>
|
<small>{{ c.subTitle }}</small>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<app-org-badge
|
||||||
|
*ngIf="!organization"
|
||||||
|
[disabled]="disabled"
|
||||||
|
[organizationId]="c.organizationId"
|
||||||
|
[organizationName]="c.organizationId | orgNameFromId : (organizations$ | async)"
|
||||||
|
appStopProp
|
||||||
|
>
|
||||||
|
</app-org-badge>
|
||||||
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<span bitBadge badgeType="warning">
|
<span bitBadge badgeType="warning">
|
||||||
{{ "exposedXTimes" | i18n : (exposedPasswordMap.get(c.id) | number) }}
|
{{ "exposedXTimes" | i18n : (exposedPasswordMap.get(c.id) | number) }}
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
|
||||||
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
|
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 { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||||
|
|
||||||
|
import { ExposedPasswordsReportComponent } from "./exposed-passwords-report.component";
|
||||||
|
import { cipherData } from "./reports-ciphers.mock";
|
||||||
|
|
||||||
|
describe("ExposedPasswordsReportComponent", () => {
|
||||||
|
let component: ExposedPasswordsReportComponent;
|
||||||
|
let fixture: ComponentFixture<ExposedPasswordsReportComponent>;
|
||||||
|
let auditService: MockProxy<AuditService>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
auditService = mock<AuditService>();
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ExposedPasswordsReportComponent, I18nPipe],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: CipherService,
|
||||||
|
useValue: mock<CipherService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: AuditService,
|
||||||
|
useValue: auditService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: OrganizationService,
|
||||||
|
useValue: mock<OrganizationService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ModalService,
|
||||||
|
useValue: mock<ModalService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PasswordRepromptService,
|
||||||
|
useValue: mock<PasswordRepromptService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useValue: mock<I18nService>(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
schemas: [],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ExposedPasswordsReportComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should initialize component", () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get only ciphers with exposed passwords that the user has "Can Edit" access to', async () => {
|
||||||
|
const expectedIdOne: any = "cbea34a8-bde4-46ad-9d19-b05001228ab2";
|
||||||
|
const expectedIdTwo = "cbea34a8-bde4-46ad-9d19-b05001228cd3";
|
||||||
|
|
||||||
|
jest.spyOn(auditService, "passwordLeaked").mockReturnValue(Promise.resolve<any>(1234));
|
||||||
|
jest.spyOn(component as any, "getAllCiphers").mockReturnValue(Promise.resolve<any>(cipherData));
|
||||||
|
await component.setCiphers();
|
||||||
|
|
||||||
|
expect(component.ciphers.length).toEqual(2);
|
||||||
|
expect(component.ciphers[0].id).toEqual(expectedIdOne);
|
||||||
|
expect(component.ciphers[0].edit).toEqual(true);
|
||||||
|
expect(component.ciphers[1].id).toEqual(expectedIdTwo);
|
||||||
|
expect(component.ciphers[1].edit).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,7 +2,7 @@ import { Component, OnInit } from "@angular/core";
|
|||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
@@ -16,15 +16,16 @@ import { CipherReportComponent } from "./cipher-report.component";
|
|||||||
})
|
})
|
||||||
export class ExposedPasswordsReportComponent extends CipherReportComponent implements OnInit {
|
export class ExposedPasswordsReportComponent extends CipherReportComponent implements OnInit {
|
||||||
exposedPasswordMap = new Map<string, number>();
|
exposedPasswordMap = new Map<string, number>();
|
||||||
|
disabled = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected cipherService: CipherService,
|
protected cipherService: CipherService,
|
||||||
protected auditService: AuditService,
|
protected auditService: AuditService,
|
||||||
|
protected organizationService: OrganizationService,
|
||||||
modalService: ModalService,
|
modalService: ModalService,
|
||||||
messagingService: MessagingService,
|
|
||||||
passwordRepromptService: PasswordRepromptService
|
passwordRepromptService: PasswordRepromptService
|
||||||
) {
|
) {
|
||||||
super(modalService, messagingService, true, passwordRepromptService);
|
super(modalService, passwordRepromptService, organizationService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
@@ -35,25 +36,28 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple
|
|||||||
const allCiphers = await this.getAllCiphers();
|
const allCiphers = await this.getAllCiphers();
|
||||||
const exposedPasswordCiphers: CipherView[] = [];
|
const exposedPasswordCiphers: CipherView[] = [];
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
allCiphers.forEach((c) => {
|
allCiphers.forEach((ciph) => {
|
||||||
|
const { type, login, isDeleted, edit, viewPassword, id } = ciph;
|
||||||
if (
|
if (
|
||||||
c.type !== CipherType.Login ||
|
type !== CipherType.Login ||
|
||||||
c.login.password == null ||
|
login.password == null ||
|
||||||
c.login.password === "" ||
|
login.password === "" ||
|
||||||
c.isDeleted
|
isDeleted ||
|
||||||
|
(!this.organization && !edit) ||
|
||||||
|
!viewPassword
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const promise = this.auditService.passwordLeaked(c.login.password).then((exposedCount) => {
|
const promise = this.auditService.passwordLeaked(login.password).then((exposedCount) => {
|
||||||
if (exposedCount > 0) {
|
if (exposedCount > 0) {
|
||||||
exposedPasswordCiphers.push(c);
|
exposedPasswordCiphers.push(ciph);
|
||||||
this.exposedPasswordMap.set(c.id, exposedCount);
|
this.exposedPasswordMap.set(id, exposedCount);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
promises.push(promise);
|
promises.push(promise);
|
||||||
});
|
});
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
this.ciphers = exposedPasswordCiphers;
|
this.ciphers = [...exposedPasswordCiphers];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getAllCiphers(): Promise<CipherView[]> {
|
protected getAllCiphers(): Promise<CipherView[]> {
|
||||||
|
|||||||
@@ -59,6 +59,16 @@
|
|||||||
<br />
|
<br />
|
||||||
<small>{{ c.subTitle }}</small>
|
<small>{{ c.subTitle }}</small>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<app-org-badge
|
||||||
|
*ngIf="!organization"
|
||||||
|
[disabled]="disabled"
|
||||||
|
[organizationId]="c.organizationId"
|
||||||
|
[organizationName]="c.organizationId | orgNameFromId : (organizations$ | async)"
|
||||||
|
appStopProp
|
||||||
|
>
|
||||||
|
</app-org-badge>
|
||||||
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<a
|
<a
|
||||||
bitBadge
|
bitBadge
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
|
import { mock } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
|
||||||
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||||
|
|
||||||
|
import { InactiveTwoFactorReportComponent } from "./inactive-two-factor-report.component";
|
||||||
|
import { cipherData } from "./reports-ciphers.mock";
|
||||||
|
|
||||||
|
describe("InactiveTwoFactorReportComponent", () => {
|
||||||
|
let component: InactiveTwoFactorReportComponent;
|
||||||
|
let fixture: ComponentFixture<InactiveTwoFactorReportComponent>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [InactiveTwoFactorReportComponent, I18nPipe],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: CipherService,
|
||||||
|
useValue: mock<CipherService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: OrganizationService,
|
||||||
|
useValue: mock<OrganizationService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ModalService,
|
||||||
|
useValue: mock<ModalService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: LogService,
|
||||||
|
useValue: mock<LogService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PasswordRepromptService,
|
||||||
|
useValue: mock<PasswordRepromptService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useValue: mock<I18nService>(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
schemas: [],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(InactiveTwoFactorReportComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should initialize component", () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get only ciphers with domains in the 2fa directory that they have "Can Edit" access to', async () => {
|
||||||
|
const expectedIdOne: any = "cbea34a8-bde4-46ad-9d19-b05001228xy4";
|
||||||
|
const expectedIdTwo: any = "cbea34a8-bde4-46ad-9d19-b05001227nm5";
|
||||||
|
component.services.set(
|
||||||
|
"101domain.com",
|
||||||
|
"https://help.101domain.com/account-management/account-security/enabling-disabling-two-factor-verification"
|
||||||
|
);
|
||||||
|
component.services.set(
|
||||||
|
"123formbuilder.com",
|
||||||
|
"https://www.123formbuilder.com/docs/multi-factor-authentication-login"
|
||||||
|
);
|
||||||
|
|
||||||
|
jest.spyOn(component as any, "getAllCiphers").mockReturnValue(Promise.resolve<any>(cipherData));
|
||||||
|
await component.setCiphers();
|
||||||
|
|
||||||
|
expect(component.ciphers.length).toEqual(2);
|
||||||
|
expect(component.ciphers[0].id).toEqual(expectedIdOne);
|
||||||
|
expect(component.ciphers[0].edit).toEqual(true);
|
||||||
|
expect(component.ciphers[1].id).toEqual(expectedIdTwo);
|
||||||
|
expect(component.ciphers[1].edit).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||||
@@ -18,15 +18,16 @@ import { CipherReportComponent } from "./cipher-report.component";
|
|||||||
export class InactiveTwoFactorReportComponent extends CipherReportComponent implements OnInit {
|
export class InactiveTwoFactorReportComponent extends CipherReportComponent implements OnInit {
|
||||||
services = new Map<string, string>();
|
services = new Map<string, string>();
|
||||||
cipherDocs = new Map<string, string>();
|
cipherDocs = new Map<string, string>();
|
||||||
|
disabled = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected cipherService: CipherService,
|
protected cipherService: CipherService,
|
||||||
|
protected organizationService: OrganizationService,
|
||||||
modalService: ModalService,
|
modalService: ModalService,
|
||||||
messagingService: MessagingService,
|
|
||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
passwordRepromptService: PasswordRepromptService
|
passwordRepromptService: PasswordRepromptService
|
||||||
) {
|
) {
|
||||||
super(modalService, messagingService, true, passwordRepromptService);
|
super(modalService, passwordRepromptService, organizationService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
@@ -43,33 +44,34 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl
|
|||||||
if (this.services.size > 0) {
|
if (this.services.size > 0) {
|
||||||
const allCiphers = await this.getAllCiphers();
|
const allCiphers = await this.getAllCiphers();
|
||||||
const inactive2faCiphers: CipherView[] = [];
|
const inactive2faCiphers: CipherView[] = [];
|
||||||
const promises: Promise<void>[] = [];
|
|
||||||
const docs = new Map<string, string>();
|
const docs = new Map<string, string>();
|
||||||
allCiphers.forEach((c) => {
|
|
||||||
|
allCiphers.forEach((ciph) => {
|
||||||
|
const { type, login, isDeleted, edit, id } = ciph;
|
||||||
if (
|
if (
|
||||||
c.type !== CipherType.Login ||
|
type !== CipherType.Login ||
|
||||||
(c.login.totp != null && c.login.totp !== "") ||
|
(login.totp != null && login.totp !== "") ||
|
||||||
!c.login.hasUris ||
|
!login.hasUris ||
|
||||||
c.isDeleted
|
isDeleted ||
|
||||||
|
(!this.organization && !edit)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (let i = 0; i < c.login.uris.length; i++) {
|
for (let i = 0; i < login.uris.length; i++) {
|
||||||
const u = c.login.uris[i];
|
const u = login.uris[i];
|
||||||
if (u.uri != null && u.uri !== "") {
|
if (u.uri != null && u.uri !== "") {
|
||||||
const uri = u.uri.replace("www.", "");
|
const uri = u.uri.replace("www.", "");
|
||||||
const domain = Utils.getDomain(uri);
|
const domain = Utils.getDomain(uri);
|
||||||
if (domain != null && this.services.has(domain)) {
|
if (domain != null && this.services.has(domain)) {
|
||||||
if (this.services.get(domain) != null) {
|
if (this.services.get(domain) != null) {
|
||||||
docs.set(c.id, this.services.get(domain));
|
docs.set(id, this.services.get(domain));
|
||||||
}
|
}
|
||||||
inactive2faCiphers.push(c);
|
inactive2faCiphers.push(ciph);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await Promise.all(promises);
|
this.ciphers = [...inactive2faCiphers];
|
||||||
this.ciphers = inactive2faCiphers;
|
|
||||||
this.cipherDocs = docs;
|
this.cipherDocs = docs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
128
apps/web/src/app/reports/pages/reports-ciphers.mock.ts
Normal file
128
apps/web/src/app/reports/pages/reports-ciphers.mock.ts
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
export const cipherData: any[] = [
|
||||||
|
{
|
||||||
|
initializerKey: 1,
|
||||||
|
id: "cbea34a8-bde4-46ad-9d19-b05001228ab1",
|
||||||
|
organizationId: null,
|
||||||
|
folderId: null,
|
||||||
|
name: "Cannot Be Edited",
|
||||||
|
notes: null,
|
||||||
|
isDeleted: false,
|
||||||
|
type: 1,
|
||||||
|
favorite: false,
|
||||||
|
organizationUseTotp: false,
|
||||||
|
login: {
|
||||||
|
password: "123",
|
||||||
|
},
|
||||||
|
edit: false,
|
||||||
|
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-b05001228ab2",
|
||||||
|
organizationId: null,
|
||||||
|
folderId: null,
|
||||||
|
name: "Can Be Edited id ending 2",
|
||||||
|
notes: null,
|
||||||
|
isDeleted: false,
|
||||||
|
type: 1,
|
||||||
|
favorite: false,
|
||||||
|
organizationUseTotp: false,
|
||||||
|
login: {
|
||||||
|
password: "123",
|
||||||
|
hasUris: true,
|
||||||
|
uris: [
|
||||||
|
{
|
||||||
|
uri: "http://nothing.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-b05001228cd3",
|
||||||
|
organizationId: null,
|
||||||
|
folderId: null,
|
||||||
|
name: "Can Be Edited id ending 3",
|
||||||
|
notes: null,
|
||||||
|
type: 1,
|
||||||
|
favorite: false,
|
||||||
|
organizationUseTotp: false,
|
||||||
|
login: {
|
||||||
|
password: "123",
|
||||||
|
hasUris: true,
|
||||||
|
uris: [
|
||||||
|
{
|
||||||
|
uri: "http://example.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-b05001228xy4",
|
||||||
|
organizationId: null,
|
||||||
|
folderId: null,
|
||||||
|
name: "Can Be Edited id ending 4",
|
||||||
|
notes: null,
|
||||||
|
type: 1,
|
||||||
|
favorite: false,
|
||||||
|
organizationUseTotp: false,
|
||||||
|
login: {
|
||||||
|
hasUris: true,
|
||||||
|
uris: [{ uri: "101domain.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-b05001227nm5",
|
||||||
|
organizationId: null,
|
||||||
|
folderId: null,
|
||||||
|
name: "Can Be Edited id ending 5",
|
||||||
|
notes: null,
|
||||||
|
type: 1,
|
||||||
|
favorite: false,
|
||||||
|
organizationUseTotp: false,
|
||||||
|
login: {
|
||||||
|
hasUris: true,
|
||||||
|
uris: [{ uri: "123formbuilder.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,
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -64,6 +64,16 @@
|
|||||||
<br />
|
<br />
|
||||||
<small>{{ c.subTitle }}</small>
|
<small>{{ c.subTitle }}</small>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<app-org-badge
|
||||||
|
*ngIf="!organization"
|
||||||
|
[disabled]="disabled"
|
||||||
|
[organizationId]="c.organizationId"
|
||||||
|
[organizationName]="c.organizationId | orgNameFromId : (organizations$ | async)"
|
||||||
|
appStopProp
|
||||||
|
>
|
||||||
|
</app-org-badge>
|
||||||
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<span bitBadge badgeType="warning">
|
<span bitBadge badgeType="warning">
|
||||||
{{ "reusedXTimes" | i18n : passwordUseMap.get(c.login.password) }}
|
{{ "reusedXTimes" | i18n : passwordUseMap.get(c.login.password) }}
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
|
import { mock } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
|
||||||
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||||
|
|
||||||
|
import { cipherData } from "./reports-ciphers.mock";
|
||||||
|
import { ReusedPasswordsReportComponent } from "./reused-passwords-report.component";
|
||||||
|
|
||||||
|
describe("ReusedPasswordsReportComponent", () => {
|
||||||
|
let component: ReusedPasswordsReportComponent;
|
||||||
|
let fixture: ComponentFixture<ReusedPasswordsReportComponent>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ReusedPasswordsReportComponent, I18nPipe],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: CipherService,
|
||||||
|
useValue: mock<CipherService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: OrganizationService,
|
||||||
|
useValue: mock<OrganizationService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ModalService,
|
||||||
|
useValue: mock<ModalService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PasswordRepromptService,
|
||||||
|
useValue: mock<PasswordRepromptService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useValue: mock<I18nService>(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
schemas: [],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ReusedPasswordsReportComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should initialize component", () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get ciphers with reused passwords that the user has "Can Edit" access to', async () => {
|
||||||
|
const expectedIdOne: any = "cbea34a8-bde4-46ad-9d19-b05001228ab2";
|
||||||
|
const expectedIdTwo = "cbea34a8-bde4-46ad-9d19-b05001228cd3";
|
||||||
|
jest.spyOn(component as any, "getAllCiphers").mockReturnValue(Promise.resolve<any>(cipherData));
|
||||||
|
await component.setCiphers();
|
||||||
|
|
||||||
|
expect(component.ciphers.length).toEqual(2);
|
||||||
|
expect(component.ciphers[0].id).toEqual(expectedIdOne);
|
||||||
|
expect(component.ciphers[0].edit).toEqual(true);
|
||||||
|
expect(component.ciphers[1].id).toEqual(expectedIdTwo);
|
||||||
|
expect(component.ciphers[1].edit).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
@@ -16,15 +15,15 @@ import { CipherReportComponent } from "./cipher-report.component";
|
|||||||
})
|
})
|
||||||
export class ReusedPasswordsReportComponent extends CipherReportComponent implements OnInit {
|
export class ReusedPasswordsReportComponent extends CipherReportComponent implements OnInit {
|
||||||
passwordUseMap: Map<string, number>;
|
passwordUseMap: Map<string, number>;
|
||||||
|
disabled = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected cipherService: CipherService,
|
protected cipherService: CipherService,
|
||||||
|
protected organizationService: OrganizationService,
|
||||||
modalService: ModalService,
|
modalService: ModalService,
|
||||||
messagingService: MessagingService,
|
|
||||||
stateService: StateService,
|
|
||||||
passwordRepromptService: PasswordRepromptService
|
passwordRepromptService: PasswordRepromptService
|
||||||
) {
|
) {
|
||||||
super(modalService, messagingService, true, passwordRepromptService);
|
super(modalService, passwordRepromptService, organizationService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
@@ -35,20 +34,23 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem
|
|||||||
const allCiphers = await this.getAllCiphers();
|
const allCiphers = await this.getAllCiphers();
|
||||||
const ciphersWithPasswords: CipherView[] = [];
|
const ciphersWithPasswords: CipherView[] = [];
|
||||||
this.passwordUseMap = new Map<string, number>();
|
this.passwordUseMap = new Map<string, number>();
|
||||||
allCiphers.forEach((c) => {
|
allCiphers.forEach((ciph) => {
|
||||||
|
const { type, login, isDeleted, edit, viewPassword } = ciph;
|
||||||
if (
|
if (
|
||||||
c.type !== CipherType.Login ||
|
type !== CipherType.Login ||
|
||||||
c.login.password == null ||
|
login.password == null ||
|
||||||
c.login.password === "" ||
|
login.password === "" ||
|
||||||
c.isDeleted
|
isDeleted ||
|
||||||
|
(!this.organization && !edit) ||
|
||||||
|
!viewPassword
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ciphersWithPasswords.push(c);
|
ciphersWithPasswords.push(ciph);
|
||||||
if (this.passwordUseMap.has(c.login.password)) {
|
if (this.passwordUseMap.has(login.password)) {
|
||||||
this.passwordUseMap.set(c.login.password, this.passwordUseMap.get(c.login.password) + 1);
|
this.passwordUseMap.set(login.password, this.passwordUseMap.get(login.password) + 1);
|
||||||
} else {
|
} else {
|
||||||
this.passwordUseMap.set(c.login.password, 1);
|
this.passwordUseMap.set(login.password, 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const reusedPasswordCiphers = ciphersWithPasswords.filter(
|
const reusedPasswordCiphers = ciphersWithPasswords.filter(
|
||||||
|
|||||||
@@ -59,6 +59,16 @@
|
|||||||
<br />
|
<br />
|
||||||
<small>{{ c.subTitle }}</small>
|
<small>{{ c.subTitle }}</small>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<app-org-badge
|
||||||
|
*ngIf="!organization"
|
||||||
|
[disabled]="disabled"
|
||||||
|
[organizationId]="c.organizationId"
|
||||||
|
[organizationName]="c.organizationId | orgNameFromId : (organizations$ | async)"
|
||||||
|
appStopProp
|
||||||
|
>
|
||||||
|
</app-org-badge>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
|
import { mock } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
|
||||||
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
|
import { PasswordRepromptService } from "@bitwarden/vault";
|
||||||
|
|
||||||
|
import { cipherData } from "./reports-ciphers.mock";
|
||||||
|
import { UnsecuredWebsitesReportComponent } from "./unsecured-websites-report.component";
|
||||||
|
|
||||||
|
describe("UnsecuredWebsitesReportComponent", () => {
|
||||||
|
let component: UnsecuredWebsitesReportComponent;
|
||||||
|
let fixture: ComponentFixture<UnsecuredWebsitesReportComponent>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [UnsecuredWebsitesReportComponent, I18nPipe],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: CipherService,
|
||||||
|
useValue: mock<CipherService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: OrganizationService,
|
||||||
|
useValue: mock<OrganizationService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ModalService,
|
||||||
|
useValue: mock<ModalService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PasswordRepromptService,
|
||||||
|
useValue: mock<PasswordRepromptService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useValue: mock<I18nService>(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
schemas: [],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(UnsecuredWebsitesReportComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should initialize component", () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get only unsecured ciphers that the user has "Can Edit" access to', async () => {
|
||||||
|
const expectedIdOne: any = "cbea34a8-bde4-46ad-9d19-b05001228ab2";
|
||||||
|
const expectedIdTwo = "cbea34a8-bde4-46ad-9d19-b05001228cd3";
|
||||||
|
jest.spyOn(component as any, "getAllCiphers").mockReturnValue(Promise.resolve<any>(cipherData));
|
||||||
|
await component.setCiphers();
|
||||||
|
|
||||||
|
expect(component.ciphers.length).toEqual(2);
|
||||||
|
expect(component.ciphers[0].id).toEqual(expectedIdOne);
|
||||||
|
expect(component.ciphers[0].edit).toEqual(true);
|
||||||
|
expect(component.ciphers[1].id).toEqual(expectedIdTwo);
|
||||||
|
expect(component.ciphers[1].edit).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||||
@@ -14,13 +14,15 @@ import { CipherReportComponent } from "./cipher-report.component";
|
|||||||
templateUrl: "unsecured-websites-report.component.html",
|
templateUrl: "unsecured-websites-report.component.html",
|
||||||
})
|
})
|
||||||
export class UnsecuredWebsitesReportComponent extends CipherReportComponent implements OnInit {
|
export class UnsecuredWebsitesReportComponent extends CipherReportComponent implements OnInit {
|
||||||
|
disabled = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected cipherService: CipherService,
|
protected cipherService: CipherService,
|
||||||
|
protected organizationService: OrganizationService,
|
||||||
modalService: ModalService,
|
modalService: ModalService,
|
||||||
messagingService: MessagingService,
|
|
||||||
passwordRepromptService: PasswordRepromptService
|
passwordRepromptService: PasswordRepromptService
|
||||||
) {
|
) {
|
||||||
super(modalService, messagingService, true, passwordRepromptService);
|
super(modalService, passwordRepromptService, organizationService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
@@ -35,7 +37,9 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl
|
|||||||
}
|
}
|
||||||
return c.login.uris.some((u) => u.uri != null && u.uri.indexOf("http://") === 0);
|
return c.login.uris.some((u) => u.uri != null && u.uri.indexOf("http://") === 0);
|
||||||
});
|
});
|
||||||
this.ciphers = unsecuredCiphers;
|
this.ciphers = unsecuredCiphers.filter(
|
||||||
|
(c) => (!this.organization && c.edit) || (this.organization && !c.edit)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getAllCiphers(): Promise<CipherView[]> {
|
protected getAllCiphers(): Promise<CipherView[]> {
|
||||||
|
|||||||
@@ -64,6 +64,16 @@
|
|||||||
<br />
|
<br />
|
||||||
<small>{{ c.subTitle }}</small>
|
<small>{{ c.subTitle }}</small>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<app-org-badge
|
||||||
|
*ngIf="!organization"
|
||||||
|
[disabled]="disabled"
|
||||||
|
[organizationId]="c.organizationId"
|
||||||
|
[organizationName]="c.organizationId | orgNameFromId : (organizations$ | async)"
|
||||||
|
appStopProp
|
||||||
|
>
|
||||||
|
</app-org-badge>
|
||||||
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<span bitBadge [badgeType]="passwordStrengthMap.get(c.id)[1]">
|
<span bitBadge [badgeType]="passwordStrengthMap.get(c.id)[1]">
|
||||||
{{ passwordStrengthMap.get(c.id)[0] | i18n }}
|
{{ passwordStrengthMap.get(c.id)[0] | i18n }}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// eslint-disable-next-line no-restricted-imports
|
||||||
|
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||||
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
|
||||||
|
import { ModalService } from "@bitwarden/angular/services/modal.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 { PasswordRepromptService } from "@bitwarden/vault";
|
||||||
|
|
||||||
|
import { cipherData } from "./reports-ciphers.mock";
|
||||||
|
import { WeakPasswordsReportComponent } from "./weak-passwords-report.component";
|
||||||
|
|
||||||
|
describe("WeakPasswordsReportComponent", () => {
|
||||||
|
let component: WeakPasswordsReportComponent;
|
||||||
|
let fixture: ComponentFixture<WeakPasswordsReportComponent>;
|
||||||
|
let passwordStrengthService: MockProxy<PasswordStrengthServiceAbstraction>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
passwordStrengthService = mock<PasswordStrengthServiceAbstraction>();
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [WeakPasswordsReportComponent, I18nPipe],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: CipherService,
|
||||||
|
useValue: mock<CipherService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PasswordStrengthServiceAbstraction,
|
||||||
|
useValue: passwordStrengthService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: OrganizationService,
|
||||||
|
useValue: mock<OrganizationService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ModalService,
|
||||||
|
useValue: mock<ModalService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PasswordRepromptService,
|
||||||
|
useValue: mock<PasswordRepromptService>(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: I18nService,
|
||||||
|
useValue: mock<I18nService>(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
schemas: [],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(WeakPasswordsReportComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should initialize component", () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get only ciphers with weak passwords that the user has "Can Edit" access to', async () => {
|
||||||
|
const expectedIdOne: any = "cbea34a8-bde4-46ad-9d19-b05001228ab2";
|
||||||
|
const expectedIdTwo = "cbea34a8-bde4-46ad-9d19-b05001228cd3";
|
||||||
|
|
||||||
|
jest.spyOn(passwordStrengthService, "getPasswordStrength").mockReturnValue({
|
||||||
|
password: "123",
|
||||||
|
score: 0,
|
||||||
|
} as any);
|
||||||
|
jest.spyOn(component as any, "getAllCiphers").mockReturnValue(Promise.resolve<any>(cipherData));
|
||||||
|
await component.setCiphers();
|
||||||
|
|
||||||
|
expect(component.ciphers.length).toEqual(2);
|
||||||
|
expect(component.ciphers[0].id).toEqual(expectedIdOne);
|
||||||
|
expect(component.ciphers[0].edit).toEqual(true);
|
||||||
|
expect(component.ciphers[1].id).toEqual(expectedIdTwo);
|
||||||
|
expect(component.ciphers[1].edit).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
|
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
import { CipherType } from "@bitwarden/common/vault/enums/cipher-type";
|
||||||
@@ -17,17 +18,19 @@ import { CipherReportComponent } from "./cipher-report.component";
|
|||||||
})
|
})
|
||||||
export class WeakPasswordsReportComponent extends CipherReportComponent implements OnInit {
|
export class WeakPasswordsReportComponent extends CipherReportComponent implements OnInit {
|
||||||
passwordStrengthMap = new Map<string, [string, BadgeTypes]>();
|
passwordStrengthMap = new Map<string, [string, BadgeTypes]>();
|
||||||
|
disabled = true;
|
||||||
|
|
||||||
private passwordStrengthCache = new Map<string, number>();
|
private passwordStrengthCache = new Map<string, number>();
|
||||||
|
weakPasswordCiphers: CipherView[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected cipherService: CipherService,
|
protected cipherService: CipherService,
|
||||||
protected passwordStrengthService: PasswordStrengthServiceAbstraction,
|
protected passwordStrengthService: PasswordStrengthServiceAbstraction,
|
||||||
|
protected organizationService: OrganizationService,
|
||||||
modalService: ModalService,
|
modalService: ModalService,
|
||||||
messagingService: MessagingService,
|
|
||||||
passwordRepromptService: PasswordRepromptService
|
passwordRepromptService: PasswordRepromptService
|
||||||
) {
|
) {
|
||||||
super(modalService, messagingService, true, passwordRepromptService);
|
super(modalService, passwordRepromptService, organizationService);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
@@ -36,33 +39,32 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen
|
|||||||
|
|
||||||
async setCiphers() {
|
async setCiphers() {
|
||||||
const allCiphers = await this.getAllCiphers();
|
const allCiphers = await this.getAllCiphers();
|
||||||
const weakPasswordCiphers: CipherView[] = [];
|
this.findWeakPasswords(allCiphers);
|
||||||
const isUserNameNotEmpty = (c: CipherView): boolean => {
|
}
|
||||||
return c.login.username != null && c.login.username.trim() !== "";
|
|
||||||
};
|
|
||||||
const getCacheKey = (c: CipherView): string => {
|
|
||||||
return c.login.password + "_____" + (isUserNameNotEmpty(c) ? c.login.username : "");
|
|
||||||
};
|
|
||||||
|
|
||||||
allCiphers.forEach((c) => {
|
protected findWeakPasswords(ciphers: any[]): void {
|
||||||
|
ciphers.forEach((ciph) => {
|
||||||
|
const { type, login, isDeleted, edit, viewPassword, id } = ciph;
|
||||||
if (
|
if (
|
||||||
c.type !== CipherType.Login ||
|
type !== CipherType.Login ||
|
||||||
c.login.password == null ||
|
login.password == null ||
|
||||||
c.login.password === "" ||
|
login.password === "" ||
|
||||||
c.isDeleted
|
isDeleted ||
|
||||||
|
(!this.organization && !edit) ||
|
||||||
|
!viewPassword
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const hasUserName = isUserNameNotEmpty(c);
|
const hasUserName = this.isUserNameNotEmpty(ciph);
|
||||||
const cacheKey = getCacheKey(c);
|
const cacheKey = this.getCacheKey(ciph);
|
||||||
if (!this.passwordStrengthCache.has(cacheKey)) {
|
if (!this.passwordStrengthCache.has(cacheKey)) {
|
||||||
let userInput: string[] = [];
|
let userInput: string[] = [];
|
||||||
if (hasUserName) {
|
if (hasUserName) {
|
||||||
const atPosition = c.login.username.indexOf("@");
|
const atPosition = login.username.indexOf("@");
|
||||||
if (atPosition > -1) {
|
if (atPosition > -1) {
|
||||||
userInput = userInput
|
userInput = userInput
|
||||||
.concat(
|
.concat(
|
||||||
c.login.username
|
login.username
|
||||||
.substr(0, atPosition)
|
.substr(0, atPosition)
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
@@ -70,15 +72,15 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen
|
|||||||
)
|
)
|
||||||
.filter((i) => i.length >= 3);
|
.filter((i) => i.length >= 3);
|
||||||
} else {
|
} else {
|
||||||
userInput = c.login.username
|
userInput = login.username
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.split(/[^A-Za-z0-9]/)
|
.split(/[^A-Za-z0-9]/)
|
||||||
.filter((i) => i.length >= 3);
|
.filter((i: any) => i.length >= 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const result = this.passwordStrengthService.getPasswordStrength(
|
const result = this.passwordStrengthService.getPasswordStrength(
|
||||||
c.login.password,
|
login.password,
|
||||||
null,
|
null,
|
||||||
userInput.length > 0 ? userInput : null
|
userInput.length > 0 ? userInput : null
|
||||||
);
|
);
|
||||||
@@ -86,17 +88,17 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen
|
|||||||
}
|
}
|
||||||
const score = this.passwordStrengthCache.get(cacheKey);
|
const score = this.passwordStrengthCache.get(cacheKey);
|
||||||
if (score != null && score <= 2) {
|
if (score != null && score <= 2) {
|
||||||
this.passwordStrengthMap.set(c.id, this.scoreKey(score));
|
this.passwordStrengthMap.set(id, this.scoreKey(score));
|
||||||
weakPasswordCiphers.push(c);
|
this.weakPasswordCiphers.push(ciph);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
weakPasswordCiphers.sort((a, b) => {
|
this.weakPasswordCiphers.sort((a, b) => {
|
||||||
return (
|
return (
|
||||||
this.passwordStrengthCache.get(getCacheKey(a)) -
|
this.passwordStrengthCache.get(this.getCacheKey(a)) -
|
||||||
this.passwordStrengthCache.get(getCacheKey(b))
|
this.passwordStrengthCache.get(this.getCacheKey(b))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
this.ciphers = weakPasswordCiphers;
|
this.ciphers = [...this.weakPasswordCiphers];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getAllCiphers(): Promise<CipherView[]> {
|
protected getAllCiphers(): Promise<CipherView[]> {
|
||||||
@@ -108,6 +110,14 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isUserNameNotEmpty(c: CipherView): boolean {
|
||||||
|
return !Utils.isNullOrWhitespace(c.login.username);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCacheKey(c: CipherView): string {
|
||||||
|
return c.login.password + "_____" + (this.isUserNameNotEmpty(c) ? c.login.username : "");
|
||||||
|
}
|
||||||
|
|
||||||
private scoreKey(score: number): [string, BadgeTypes] {
|
private scoreKey(score: number): [string, BadgeTypes] {
|
||||||
switch (score) {
|
switch (score) {
|
||||||
case 4:
|
case 4:
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { CommonModule } from "@angular/common";
|
|||||||
import { NgModule } from "@angular/core";
|
import { NgModule } from "@angular/core";
|
||||||
|
|
||||||
import { SharedModule } from "../shared";
|
import { SharedModule } from "../shared";
|
||||||
|
import { OrganizationBadgeModule } from "../vault/individual-vault/organization-badge/organization-badge.module";
|
||||||
|
import { PipesModule } from "../vault/individual-vault/pipes/pipes.module";
|
||||||
|
|
||||||
import { BreachReportComponent } from "./pages/breach-report.component";
|
import { BreachReportComponent } from "./pages/breach-report.component";
|
||||||
import { ExposedPasswordsReportComponent } from "./pages/exposed-passwords-report.component";
|
import { ExposedPasswordsReportComponent } from "./pages/exposed-passwords-report.component";
|
||||||
@@ -15,7 +17,14 @@ import { ReportsRoutingModule } from "./reports-routing.module";
|
|||||||
import { ReportsSharedModule } from "./shared";
|
import { ReportsSharedModule } from "./shared";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, SharedModule, ReportsSharedModule, ReportsRoutingModule],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
ReportsSharedModule,
|
||||||
|
ReportsRoutingModule,
|
||||||
|
OrganizationBadgeModule,
|
||||||
|
PipesModule,
|
||||||
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
BreachReportComponent,
|
BreachReportComponent,
|
||||||
ExposedPasswordsReportComponent,
|
ExposedPasswordsReportComponent,
|
||||||
@@ -25,7 +34,6 @@ import { ReportsSharedModule } from "./shared";
|
|||||||
ReusedPasswordsReportComponent,
|
ReusedPasswordsReportComponent,
|
||||||
UnsecuredWebsitesReportComponent,
|
UnsecuredWebsitesReportComponent,
|
||||||
WeakPasswordsReportComponent,
|
WeakPasswordsReportComponent,
|
||||||
WeakPasswordsReportComponent,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ReportsModule {}
|
export class ReportsModule {}
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ import { AddEditComponent } from "../vault/individual-vault/add-edit.component";
|
|||||||
import { AttachmentsComponent } from "../vault/individual-vault/attachments.component";
|
import { AttachmentsComponent } from "../vault/individual-vault/attachments.component";
|
||||||
import { CollectionsComponent } from "../vault/individual-vault/collections.component";
|
import { CollectionsComponent } from "../vault/individual-vault/collections.component";
|
||||||
import { FolderAddEditComponent } from "../vault/individual-vault/folder-add-edit.component";
|
import { FolderAddEditComponent } from "../vault/individual-vault/folder-add-edit.component";
|
||||||
|
import { OrganizationBadgeModule } from "../vault/individual-vault/organization-badge/organization-badge.module";
|
||||||
|
import { PipesModule } from "../vault/individual-vault/pipes/pipes.module";
|
||||||
import { ShareComponent } from "../vault/individual-vault/share.component";
|
import { ShareComponent } from "../vault/individual-vault/share.component";
|
||||||
import { AddEditComponent as OrgAddEditComponent } from "../vault/org-vault/add-edit.component";
|
import { AddEditComponent as OrgAddEditComponent } from "../vault/org-vault/add-edit.component";
|
||||||
import { AttachmentsComponent as OrgAttachmentsComponent } from "../vault/org-vault/attachments.component";
|
import { AttachmentsComponent as OrgAttachmentsComponent } from "../vault/org-vault/attachments.component";
|
||||||
@@ -102,6 +104,8 @@ import { SharedModule } from "./shared.module";
|
|||||||
DynamicAvatarComponent,
|
DynamicAvatarComponent,
|
||||||
EnvironmentSelectorModule,
|
EnvironmentSelectorModule,
|
||||||
AccountFingerprintComponent,
|
AccountFingerprintComponent,
|
||||||
|
OrganizationBadgeModule,
|
||||||
|
PipesModule,
|
||||||
PasswordCalloutComponent,
|
PasswordCalloutComponent,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
|||||||
Reference in New Issue
Block a user